//*****************************************************************************
// JFab (http://code.google.com/p/jfab)
// Copyright (c) 2011 JFab.org
// Admin jfab@jeffreythompson.net
//
// See the file "LICENSE.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//*****************************************************************************

package org.jfab.core.util;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Locale;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

/**
 * Provides a dynamic java class compiler.
 * 
 * Inspired by <a href=
 * "http://www.beyondlinux.com/2011/07/20/3-steps-to-dynamically-compile-instantiate-and-run-a-java-class/"
 * >this</a>.
 */
public class DynamicCompiler
{
    /**
     * Provides an implementation of a diagnostic listener.
     */
    static class MyDiagnosticListener implements
            DiagnosticListener<JavaFileObject>
    {
        @Override
        public void report(final Diagnostic<? extends JavaFileObject> diagnostic)
        {

            System.out.println("Line Number->" + diagnostic.getLineNumber());
            System.out.println("code->" + diagnostic.getCode());
            System.out.println("Message->"
                    + diagnostic.getMessage(Locale.ENGLISH));
            System.out.println("Source->" + diagnostic.getSource());
            System.out.println(" ");
        }
    }

    /**
     * Provides an in-memory java source file so there is no need to put the
     * source file on hard disk.
     */
    private static class InMemoryJavaFileObject extends SimpleJavaFileObject
    {
        /** Contents. */
        private String _contents = null;

        /**
         * Construct this object with the given parameters.
         * 
         * @param className Class name.
         * @param contents Contents.
         */
        public InMemoryJavaFileObject(final String className,
                final String contents)
        {
            super(URI.create("string:///" + className.replace('.', '/')
                    + Kind.SOURCE.extension), Kind.SOURCE);
            _contents = contents;
        }

        @Override
        public CharSequence getCharContent(final boolean ignoreEncodingErrors)
                throws IOException
        {
            return _contents;
        }
    }

    /** Location to save the compiled classes. (should exist already) */
    private final String _classOutputFolder;

    /**
     * Construct this object.
     */
    public DynamicCompiler()
    {
        this("classes");
    }

    /**
     * Construct this object. with the given parameter.
     * 
     * @param classOutputFolder Class output folder.
     */
    public DynamicCompiler(final String classOutputFolder)
    {
        _classOutputFolder = classOutputFolder;
    }

    /**
     * Compile the given Java source code.
     * 
     * @param qualifiedClassName Fully qualified class name.
     * @param javaSource Java source code.
     * 
     * @return true if and only the file compiled without errors; false
     *         otherwise.
     */
    public Boolean compile(final String qualifiedClassName,
            final String javaSource)
    {
        final JavaFileObject file = new InMemoryJavaFileObject(
                qualifiedClassName, javaSource);
        final Iterable<? extends JavaFileObject> files = Arrays.asList(file);

        final Boolean result = compile(files);

        loadClass(qualifiedClassName);

        return result;
    }

    /**
     * @param qualifiedClassName Fully qualified class name.
     * 
     * @return the class.
     */
    public Class<?> loadClass(final String qualifiedClassName)
    {
        Class<?> answer = null;
        final File file = new File(_classOutputFolder);
        try
        {
            final URL url = file.toURL();
            final URL[] urls = new URL[] { url };
            final ClassLoader loader = new URLClassLoader(urls, getClass()
                    .getClassLoader());
            answer = loader.loadClass(qualifiedClassName);
        }
        catch (final MalformedURLException e)
        {
            System.out.println(e.getClass().getName() + " " + e.getMessage());
        }
        catch (final ClassNotFoundException e)
        {
            System.out.println(e.getClass().getName() + " " + e.getMessage());
        }

        return answer;
    }

    /**
     * Compile your files by JavaCompiler.
     * 
     * @param files In-memory Java source code files.
     * 
     * @return true if and only all the files compiled without errors; false
     *         otherwise.
     */
    private Boolean compile(final Iterable<? extends JavaFileObject> files)
    {
        // Get system compiler.
        final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

        // For compilation diagnostic message processing on compilation
        // WARNING/ERROR.
        final MyDiagnosticListener diagnosticListener = new MyDiagnosticListener();
        final StandardJavaFileManager fileManager = compiler
                .getStandardFileManager(diagnosticListener, Locale.ENGLISH,
                        null);

        // Specify classes output folder.
        final Iterable<String> options = Arrays.asList("-target", "1.6", "-d",
                _classOutputFolder);

        final JavaCompiler.CompilationTask task = compiler.getTask(null,
                fileManager, diagnosticListener, options, null, files);

        final Boolean result = task.call();

        return result;
    }
}